(*--------------------------------------------------------------------------+
 |																																					|
 |		Spinning Globe Graphics Demo							 														|
 |																																					|
 |		GlobeFrames (bitmaps) from an unsuspecting Sun 3											|
 |																																					|
 |		Copyright (C) 1987, 1988 Paul Mercer																	|
 |		All rights reserved																										|
 |		Non-commercial distribution only																			|
 |																																					|
 |		Created:	pm	05/24/87				new today																|
 |		Modified: pm	06/03/87	v1.0	release																	|
 |							pm	06/19/87	v1.1	fixed shading bug in mono pixmaps				|
 |																	 use pixmaps only if depth > 1					|
 |							pm	06/30/87	v1.2	works around Juggler b2 bug with				|
 |																 	 video slot VBL task switching					|
 |							pm	01/28/88  v1.3	revved for MPW 3.0D3, misc changes			|
 |							pm	03/06/88  v1.4	added support for lsr's round WDEF,			|
 |																 	 rolled 'GlobeFrames64' file into app,	|
 |																	 now boots in background, fixed a5			|
 |																	 bug under Juggler, all user settings		|
 |																	 are now saved along with window				|
 |																	 position																|
 |							pm	06/18/88	v1.41	now with 'improved' slot VBLs'					|
 |							pm	12/26/88	v1.5	added PICT resources, imaging to target	|
 |																	 window device; tweaked alignment;			|
 |																	 improved shading; fixed cursors;				|
 |																	 added palette, Palette Mgr. support;		|
 |																	 plus lots more changes;								|
 |																	 thanx to Bruce and Darin								|
 |																																					|
 |		Globe was written as an exercise in learning Color QuickDraw.					|
 |		The goal was to write a decent demo completely in Pascal using				|
 |		only Apple documented and approved techniques.  A bit of assembler		|
 |		was neccessitated by the need to do VBL synchronization on the				|
 |		Macintosh II.  There is also a bit of experimentation with Juggler.		|
 |		Globe's heavy CPU usage makes it useful for investigating							|
 |		QuickDraw (CopyBits) performance under different conditions such as		|
 |		window placement and clipping.  The sadistic will no doubt change			|
 |		the depth of the screen at runtime to watch QuickDraw sweat.					|
 |																																					|
 |		Special thanx to Larry Rosenstein for the round WDEF.									|
 |																																					|
 |		Things worth investigating include:																		|
 |			- this code is a mess!!																							|
 |			- add dithered transfer mode option for Full Color QuickDraw				|
 |			+ convert the data files to PICTs to give PackBits some work				|
 |			+	improve shading by stealing system colors (palette manager)				|
 |			+	improve shading by checking for higher resolution/depth devices		|
 |			Ð be smart about bit depth changes (window moving, control panel)		|
 |			Ð	regeneration of pixmaps to match changing screen depth						|
 |			Ð use temporary memory calls																				|
 |			Ð	speed up CopyBits when shading and blitting (how?)								|
 |			Ð	anti-alias the pixmaps (scale down big bitmaps)										|
 |			Ð	improve resolution by using more frames														|
 |			Ð	alleviate timing interaction while being juggled									|
 |																																					|
 |		Please send your enhnancements, comments and/or questions to:					|
 |			Paul Mercer																													|
 |			P.O. Box 160165																											|
 |			Cupertino, CA  95016-0165																						|
 |																																					|
 |		If you really like this program (and/or this source),									|
 | 			please send $10 to your favorite charity.														|
 |																																					|
 |			AppleLink: MERCER1																									|
 |			UUCP:      {nsc,dual,sun}!apple!pmercer															|
 |			MCI:       SOL																											|
 |																																					|
 +--------------------------------------------------------------------------*)

PROGRAM Globe;

	USES
		MemTypes, QuickDraw, Palettes, OSIntf, ToolIntf, MacPrint, PackIntf, CursorCtl;

	{$R-} {$OV-}	{we don't need no stinkin' range or overflow checking}
	{$D+}					{ cuz we've got TMON/MacsBug}

  CONST
		{$SETC Debug := True}

		{System stuff}
		WaitNextEventTrap	= $60;
		SlotVInstallTrap	= $6f;
		UnImplentedTrap		= $9f;
		PaintWhite				= $9dc;			{window manager white erase}
		Ticks							= $16a;			{tick counter in low mem}
		{$IFC Debug}
		MonkeyLives				= $100;
		{$ENDC}
		kOptionKeyCode		= 58;
		CurAppName				=	$910;
		CurApRefNum				=	$900;
		GrayRgn						= $9ee;

		{Program stuff}
		dataID				= 128;

		{Resource IDs}
		kFirstPICT		= 128;			{PICT}
		kPalette			= 256;			{pltt}

		appleID				= 128; 			{MENU}
		fileID				= 129;
		editID				= 130;
		controlID			= 131;

		loadingID			= 128;			{DLOG}
		coloringID		= 129;
		noMemoryID		= 130;
		ioErrorID			= 131;
		aboutID				=	132;
		kOK						= 1;				{ok button}

		rectID				= 128; 			{WIND}
		roundID				=	129;

		{Menu stuff}
		appleM				= 1;				{menu handle array indices}
		fileM					= 2;
		editM					= 3;
		controlM			= 4;
		menuCount			= 4;

		aboutCommand = 1;					{Apple menu commands}

		closeCommand	= 1;				{File}
		quitCommand		= 3;

		undoCommand		= 1;				{Edit}
		cutCommand		= 3;
		copyCommand		= 4;
		pasteCommand	= 5;
		clearCommand	= 6;

		slowerCommand		= 1;			{Control}
		stepCommand			= 2;
		fasterCommand		= 3;
		syncCommand			= 5;
		alignCommand		= 7;
		timeCommand			= 9;
		roundCommand		= 10;
		paletteCommand	= 12;

	TYPE
		LongIntPtr		=	^LongInt;			{for accessing Ticks in WaitVBL}
		IntegerPtr		= ^Integer;
		BooleanPtr		= ^Boolean;			{for accessing PaintWhite in Initialize}
		BitMapPtr			= ^BitMap;			{for type coercion in CopyBits}
		RgnHandlePtr	= ^RgnHandle;		{for getting at GrayRgn in bounds checking}
		Str31Ptr			=	^Str31;

		DataRec			= RECORD				{Data resource format}
										width:	Integer;	{bitmap width}
										height:	Integer;	{bitmap height}
										count:	Integer;	{frame count including mask}
										sync:		Integer;	{0/1 VBL syncing}
										round:	Integer;	{0/1 round window}
										time:		Integer;	{0/1 frames/sec display}
										top:		Integer;	{window position}
										left:		Integer;
										speed:	Integer;	{speed}
										palette: Integer;	{use custom palette}
									END; {DataRec}


  VAR
		{$Z+}												{exported for assembler access}
		parisTicks:		LongInt;			{main video device tick count on Mac II}
		{$Z-}

		appRefNum:		Integer;			{refNum of the application's resource fork}
		frameFile:		Str255;
		myData:				DataRec;			{config structure stored in as resource in data file}
		frameWidth,
		frameHeight,
		frameCount:		Integer;
		border:				Integer;			{border of gray space}
		windLeft,
		windTop:			Integer;			{saved default window position}

		curFrame,										{current frame (2 - frameCount)}
		speed,											{frame advance delay or yieldtime w/ Juggler}
		delayCount:		Integer;			{count remaining to delay}
		syncVBL,										{sync to vbl command switch}
		displayTime,								{display frames/sec command switch}
		roundWindow,								{using Larry's round WDEF}
		usePalette,									{using green and blue ramped palette}
		doneFlag:			Boolean;			{MEL quit flag}

		timeRect:			Rect;					{rect for drawing frames/sec}
		framesDone:		Integer;			{frames/sec display vars}
		targTime:			LongInt;			{time to target 'next second' used to write frames/sec}

		frameSize:		LongInt;			{size of frame bitmap}
		frameBounds,								{rect of source frame}
		destBounds:		Rect;					{centered dest rect}
		pixDepth:			Integer;			{depth of PixMap}
		bitMaps:			ARRAY[0..100] OF BitMap;				{the bitmaps themselves ***cheezewhiz}
		pixMaps:			ARRAY[0..100] OF PixMapHandle;	{handles to pixmaps for cqd}
																{ 0 = scratch, 1 = mask, 2 - frameCount = bitmaps}
		bigPixBlock:	Ptr;					{pointer to big pixmap bit data}

		theWorld:			SysEnvRec;		{the world according to SysEnvirons}
		usePixMaps,									{use pixel maps on screens deeper than 1 b/p}
		useColor,										{only use color if 4 or more 4 bits/pix}
		useShading,									{shade only if 8 or more bits/pix}
		juggler:			Boolean;			{twitcher lives flag}
		slotVBL:			Boolean;			{has slot interrupts}

		landColor,									{pretty colors}
		waterColor,
		spaceColor,
		shadeColor,
		myForeColor,
		myBackColor:	RGBColor;
		myColors:			CTabHandle;		{my color table}

		dragRect:			Rect;					{rectangle used to mark bounds for dragging window}
		myMenus:			ARRAY [1..menuCount] OF MenuHandle; {array of handles to the menus}
		myWindow:			WindowPtr;
		myPal:				PaletteHandle;
		myEvent:			EventRecord;	{information about an event}


  PROCEDURE _DataInit;							EXTERNAL;
	PROCEDURE	InstallVBLSync;					EXTERNAL;
	PROCEDURE RemoveVBLSync;					EXTERNAL;
	PROCEDURE AlignWindow;						FORWARD;		{used by Initialize}
	PROCEDURE UpdateWindow;						FORWARD;		{used by DrawFrame}
	PROCEDURE GetDefaults;						FORWARD;
	PROCEDURE SaveDefaults;						FORWARD;
	PROCEDURE ChangeWindow;						FORWARD;
	PROCEDURE ChangePalette;					FORWARD;
	FUNCTION	GetMyDevice: GDHandle;	FORWARD;
	FUNCTION	GetMyDialog(id: Integer): DialogPtr;	FORWARD;

	{$S Initialize}		{this segment is dumped after initialization}

{+--------------------------------------------------------------------------+
 |		simplistic fatal error handler with nice centering										|
 +--------------------------------------------------------------------------+}
	PROCEDURE FatalError(id: Integer);

	VAR
		deviceRect:	Rect;
		width:			Integer;
		theDialog:	DialogPtr;
		iType:			Integer;
		iHandle:		Handle;
		iBox:				Rect;

	BEGIN
		SetCursor(arrow);
		{open this window on the target device so the Palette Manager gets
		 to set the clut up properly for the offscreen pixmaps }
	  theDialog := GetNewDialog(id, Nil, WindowPtr( -1));
		deviceRect := GetMyDevice^^.gdRect;
		width := theDialog^.portRect.right - theDialog^.portRect.left;
		MoveWindow(theDialog, (((deviceRect.right - deviceRect.left) - width) DIV 2) + deviceRect.left,
								deviceRect.top + ((deviceRect.bottom - deviceRect.top) DIV 6), False);
		ShowWindow(theDialog);

		GetDItem(theDialog, kOK, iType, iHandle, iBox);
		SetPort(theDialog);
		BeginUpdate(theDialog);
		DrawDialog(theDialog);
		PenSize(3, 3);
		InsetRect(iBox, -4, -4);
		FrameRoundRect(iBox, 16, 16);
		EndUpdate(theDialog);

		SysBeep(10);
		ModalDialog(Nil, width);

		ExitToShell;
	END; {FatalError}


{+--------------------------------------------------------------------------+
 |		is the trap available?																								|
 +--------------------------------------------------------------------------+}
	FUNCTION TrapExist(t: Integer): Boolean;

	BEGIN
		IF GetTrapAddress(t) <> GetTrapAddress(UnImplentedTrap) THEN
			TrapExist := True
		ELSE
			TrapExist := False;
	END; {TrapExist}

	FUNCTION NewOSTrapExist(t: Integer): Boolean;

	BEGIN
		IF NGetTrapAddress(t, OSTrap) <> GetTrapAddress(UnImplentedTrap) THEN
			NewOSTrapExist := True
		ELSE
			NewOSTrapExist := False;
	END; {NewOSTrapExist}


{+--------------------------------------------------------------------------+
 |		return a RGBColor record																							|
 +--------------------------------------------------------------------------+}
	FUNCTION MakeRGBColor(r, g, b: Integer): RGBColor;

	VAR
		color:	RGBColor;

	BEGIN
		WITH color DO
			BEGIN
				red := r;
				green := g;
				blue := b;
			END;
		MakeRGBColor := color;
	END; {MakeRGBColor}


{+--------------------------------------------------------------------------+
 |		Get a 'STR ' Resource																									|
 +--------------------------------------------------------------------------+}
	FUNCTION GetSTR(theID: Integer): Str255;

	VAR
		aHandle:	Handle;
		s:				Str255;

	BEGIN
		aHandle := GetResource('STR ', theID);
		HLock(aHandle);
		BlockMove(aHandle^, @s, SizeResource(aHandle));
		HUnlock(aHandle);
		ReleaseResource(aHandle);
		GetSTR := s;
	END; {GetSTR}


{+--------------------------------------------------------------------------+
 |		give time to other apps and handle updates of 'loading' dialog				|
 +--------------------------------------------------------------------------+}
	PROCEDURE BeNiceToJuggler;

	VAR
		savePort:	GrafPtr;
		w:				WindowPtr;

	BEGIN
		IF GetNextEvent(everyEvent, myEvent) THEN
			IF myEvent.what = updateEvt THEN
				BEGIN
					w := FrontWindow;
					GetPort(savePort);
					SetPort(w);
					BeginUpdate(w);
					UpdtDialog(w, w^.visRgn);
					EndUpdate(w);
					SetPort(savePort);
				END;
	END; {BeNiceToJuggler}


{+------------------------------------------------------------------------+
 |		load frames off disk																								|
 +------------------------------------------------------------------------+}
	PROCEDURE LoadFrames;

	VAR
		i, j, refNum:	Integer;
		dataForkSize:	longint;
		myPort:				GrafPort;
		oldResource:	Handle;
		myPICT:				PicHandle;

		PROCEDURE IOError(err: OSErr);

		BEGIN
			IF err <> noErr THEN
				BEGIN
					i := FSClose(refNum);		{close file, just in case}
					FatalError(ioErrorID);
				END;
		END; {IOError}

	BEGIN
		IOError(FSOpen(frameFile, 0, refNum));
		IOError(GetEOF(refNum, dataForkSize));

		IF dataForkSize <> 0 THEN
			FOR i := 1 TO frameCount DO
				BEGIN
					IOError(FSRead(refNum, frameSize, bitMaps[i].baseAddr));
					BeNiceToJuggler;
					SpinCursor(32);		{spin my beachball}
			END;

		IOError(SetEOF(refNum, 0));						{truncate the data file}
		IOError(FSClose(refNum));

		OpenPort(@myPort);
		refNum := OpenResFile(frameFile);
		UseResFile(refNum);

		j := 1;
		IF dataForkSize = 0 THEN
			FOR i := 1 TO frameCount DO
				BEGIN
					REPEAT													{this little ditty allows for sparse PICT numbering}
						myPict := PicHandle(Get1Resource('PICT', kFirstPICT + j - 1));
						IF myPICT = Nil THEN
							j := j + 1;
					UNTIL ((myPict <> Nil) OR (j = 1000));

					j := j + 1;
					SetPortBits(bitMaps[i]);
					myPort.portRect := bitMaps[i].bounds;
					EraseRect(thePort^.portRect);
					DrawPicture(myPICT, thePort^.portRect);
					BeNiceToJuggler;
					SpinCursor(32);		{spin my beachball}
			END
		ELSE
			FOR i := 1 TO frameCount DO
				BEGIN
					ClipRect(bitMaps[i].bounds);
					myPict := OpenPicture(bitMaps[i].bounds);
					CopyBits(bitMaps[i], thePort^.portBits, bitMaps[i].bounds, bitMaps[i].bounds, srcCopy, nil);
					ClosePicture;
	
					REPEAT
						SetResLoad(False);
						oldResource := Get1Resource('PICT', kFirstPICT + i - 1);
						SetResLoad(True);
						RmveResource(oldResource);
						DisposHandle(oldResource);
					UNTIL oldResource = Nil;
	
					AddResource(Handle(myPICT), 'PICT', kFirstPICT + i - 1, '');
					SetResAttrs(Handle(myPICT), resPurgeable + resChanged);
	
					BeNiceToJuggler;
					SpinCursor(32);		{spin my beachball}
			END;

		UpdateResFile(refNum);
		IF refNum <> appRefNum THEN
			CloseResFile(refNum);
	END; {LoadFrames}


{+--------------------------------------------------------------------------+
 |		process mono bitmaps																									|
 +--------------------------------------------------------------------------+}
	PROCEDURE ProcessBitMaps;

	VAR
		tPort:	GrafPort;
		i:			Integer;

	BEGIN
		OpenPort(@tPort);
		SetPortBits(bitMaps[0]);	{scratch map}
		PenPat(ltGray);
		PaintRect(frameBounds);
		ClosePort(@tPort);

		CopyBits(bitMaps[0], bitMaps[1], frameBounds, frameBounds, notSrcBic, Nil);
		FOR i := 2 TO frameCount DO
			BEGIN
				CopyBits(bitMaps[1], bitMaps[i], frameBounds, frameBounds, srcOr, Nil);
				BeNiceToJuggler;
				SpinCursor(32);
			END;
	END; {ProcessBitMaps}


{+--------------------------------------------------------------------------+
 |		process mono bitmaps into pixmaps and color them if appropriate				|
 +--------------------------------------------------------------------------+}
	PROCEDURE ProcessPixMaps;

	CONST
		kShadeRange = $0e000;					{from white-kEdgeShade}
		kEdgeShade	= $01fff;

	VAR
		i, j, k:	Integer;
		tRect:		Rect;
		tCPort:		CGrafPort;
		tHandle:	PixMapHandle;
		shadePat:	PixPatHandle;
		shadeStep, shade:	Fixed;
		bigNum:		LongInt;
		saveGDevice:	GDHandle;

	BEGIN
		saveGDevice := GetGDevice;
		SetGDevice(GetMyDevice);			{make sure weÕre on the target device}
		ChangePalette;								{for 5 bit inverse table to be built/current}

		OpenCPort(@tCPort);						{open this port so we can change colors}
		tHandle := tCPort.portPixMap;	{save portPixMap since it's going to change}

		{copy bitMaps to pixMaps and let CopyBits color them in the process}
		IF useColor THEN
			BEGIN
				RGBForeColor(landColor);
				RGBBackColor(waterColor);
			END
		ELSE
			BEGIN
				RGBForeColor(myForeColor);	{black and white only for 2 bits/pixel}
				RGBBackColor(myBackColor);
			END;
		FOR i := 1 To frameCount DO
			BEGIN
				CopyBits(bitMaps[i], BitMapPtr(pixMaps[i]^)^,
									frameBounds, frameBounds, srcCopy, Nil);
				BeNiceToJuggler;
				SpinCursor(32);
			END;

		{logically or the mask over (around) globe for background}
		RGBForeColor(spaceColor);	{always try for gray}
		FOR i := 2 To frameCount DO
			BEGIN
				CopyBits(bitMaps[1], BitMapPtr(pixMaps[i]^)^,	{***quicker to use pixmaps only?}
								 frameBounds, frameBounds, srcOr, Nil);
				BeNiceToJuggler;
				SpinCursor(32);
			END;

		{shade it nicely}
		IF useShading THEN
			BEGIN
				SetPortPix(pixMaps[0]);
				RGBBackColor(myBackColor);
				EraseRect(frameBounds);
				RGBForeColor(shadeColor);
				PenMode(addOver);						{smear the shades}

{$IFC True}
				{draw shaded gray sphere}
				bigNum := kShadeRange * $1000;
				shadeStep := bigNum DIV (frameWidth DIV 2) * $10;
				shade := kEdgeShade * $10000;
				PenSize(1, 1);
				tRect := frameBounds;
				PenMode(srcCopy);
				FOR i := (frameWidth DIV 2) DOWNTO 1 DO										{this many shades}
					BEGIN
						shadeColor := MakeRGBColor(FixRound(shade), FixRound(shade), FixRound(shade));
						RGBForeColor(shadeColor);
						shade := shade + shadeStep;
						FrameOval(tRect);
						InsetRect(tRect, 1, 1);
						BeNiceToJuggler;
						SpinCursor(32);
					END;
{$ELSEC}			{old drawing before Bruce changed the world}
				{draw shaded gray sphere}
				j := frameBounds.bottom DIV (16 * 2);			{pen size}
				FOR i := 1 TO 16 - 4 DO										{this many shades}
					BEGIN
						k := i * j;
						PenSize(k, k);
						FrameOval(frameBounds);
						BeNiceToJuggler;
						SpinCursor(32);
					END;
{$ENDC}

				{apply the shaded sphere to the frames}
				FOR i := 2 TO frameCount DO
					BEGIN
						CopyBits(BitMapPtr(pixMaps[0]^)^, BitMapPtr(pixMaps[i]^)^,
										 frameBounds, frameBounds, addMin, Nil);
						BeNiceToJuggler;
						SpinCursor(32);
					END;
			END; {IF useShading}

		SetPortPix(tHandle);					{restore portPixMap for killing}
		ClosePort(GrafPtr(@tCPort));	{dispose its structures}
		SetGDevice(saveGDevice);
	END; {ProcessPixMaps}


{+--------------------------------------------------------------------------+
 |		initialize the frame globals																					|
 +--------------------------------------------------------------------------+}
	PROCEDURE InitFrameGlobals;

	VAR
	  i, j: 			Integer;
		saveGDevice,
		myDevice:		GDHandle;

	BEGIN
		frameBounds.top := 0;
		frameBounds.left := 0;
		frameBounds.bottom := frameHeight;
		frameBounds.right := frameWidth;

		{allocate the bitmaps}
		FOR i := 0 to frameCount DO
			BEGIN
				bitMaps[i].rowBytes := frameWidth DIV 8;
				bitMaps[i].bounds := frameBounds;
				bitMaps[i].baseAddr := NewPtr(frameSize);
				SpinCursor(32);
				IF bitMaps[i].baseAddr = Nil THEN
					FatalError(noMemoryID);
			END;

		{allocate the pixmaps}
		IF usePixMaps THEN
			BEGIN
				{clone the main device's color table for the pixMaps (tech note 120)}
				myDevice := GetMyDevice;
				myColors := myDevice^^.gdPMap^^.pmTable;		{get the color table}
				j := HandToHand(Handle(myColors));					{ and clone it}
				{now convert from device color table to screen color table - thanx DG}
				WITH myColors^^ DO
					BEGIN
						FOR i := 0 TO ctSize DO
							ctTable[i].value := i;	{put in indices}
						{now clear the high bit of ctFlags to indicate it's a screen color table}
						ctFlags := BAND(ctFlags, $7fff);
					END; {WITH}

				bigPixBlock := NewPtr(frameSize * pixDepth * (frameCount + 1));
				IF bigPixBlock = Nil THEN
					FatalError(noMemoryID);

				saveGDevice := GetGDevice;
				SetGDevice(myDevice);
				FOR i := 0 to frameCount DO
					BEGIN
						pixMaps[i] := NewPixMap;
						pixMaps[i]^^.pmTable := myColors;		{use my color table ***kill old color}
						pixMaps[i]^^.baseAddr := Ptr(ORD4(bigPixBlock)  + (i * frameSize * pixDepth));
						pixMaps[i]^^.rowBytes := (frameWidth DIV 8) * pixDepth + $8000;
						pixMaps[i]^^.bounds := frameBounds;
					END;
				SetGDevice(saveGDevice);
			END; {IF usePixMaps}

	END; {InitFrameGlobals}


{+--------------------------------------------------------------------------+
 |		high level initializtion																							|
 +--------------------------------------------------------------------------+}
	PROCEDURE InitFrames;

	VAR
		savePort:		GrafPtr;
		theDialog:	DialogPtr;
		dID:				Integer;

	BEGIN
		{open this window on the target device so the Palette Manager gets
		 to set the clut up properly for the offscreen pixmaps }
		IF useShading THEN
			dID := coloringID
		ELSE
			dID := loadingID;

		theDialog := GetMyDialog(dID);

		GetPort(savePort);			{process the bitmap data appropriately}
		InitFrameGlobals;				{initialize frame data structures}
		LoadFrames;							{load the bitmaps}

		IF usePixMaps THEN
			ProcessPixMaps
		ELSE
			ProcessBitMaps;
		SetPort(savePort);

		DisposDialog(theDialog);
	END; {InitFrames}


{+--------------------------------------------------------------------------+
 |		initialize applicaton globals																					|
 +--------------------------------------------------------------------------+}
	PROCEDURE InitGlobals;

	VAR
		i, j:			Integer;
		tGDevice:	GDHandle;
		refNum:		Integer;
		tHandle:	Handle;
		numFiles:	Integer;
		message:	Integer;
    document:	AppFile;
		myName:		Str31Ptr;
		appRefPtr: IntegerPtr;

	BEGIN
		IF SysEnvirons(1, theWorld) <> noErr THEN
			BEGIN
				SysBeep(5);
				SysBeep(5);
				SysBeep(5);
				ExitToShell;		{everyone should use the latest system software!}
			END;

		myWindow := Nil;
		appRefPtr := IntegerPtr(CurApRefNum);
		appRefNum := appRefPtr^;

		IF TrapExist(WaitNextEventTrap) THEN
			juggler := True
		ELSE
			juggler := False;

		IF  NewOSTrapExist(SlotVInstallTrap) THEN
			slotVBL := True
		ELSE
			slotVBL := False;

		CountAppFiles(message, numfiles);		{get Finder info}
		IF numFiles = 0 THEN
			BEGIN
				myName := Str31Ptr(CurAppName);
				frameFile := myName^;
			END
		ELSE
			BEGIN
				GetAppFiles(1, document);
				frameFile := document.fName;
				IF SetVol(Nil, document.vRefNum) <> noErr THEN
					FatalError(ioErrorID);
			END;

		GetDefaults;

		IF theWorld.hasColorQD THEN
			BEGIN
				tGDevice := GetMyDevice;
				pixDepth := tGDevice^^.GDPMap^^.pixelSize;
				usePixMaps := pixDepth > 1;
				useColor := pixDepth >= 4;
				useShading := pixDepth >= 8;
			END
		ELSE
			BEGIN
				usePixMaps := False;
				useColor := False;
				pixDepth := 1;
			END;

		frameSize := (frameWidth DIV 8) * frameHeight;
		doneFlag := False;
		curFrame := 2;
		targTime := 0;
		framesDone := 0;

		WITH screenBits.bounds DO
			SetRect(dragRect, 4, 24, right - 4, bottom - 4);

		shadeColor := MakeRGBColor($1fff, $1fff, $1fff);		{light gray for additive shading}
		landColor := MakeRGBColor(0, -1, 0);								{green continents}
		waterColor := MakeRGBColor(0, 0, -1);								{blue oceans}
		spaceColor := MakeRGBColor($1fff, $1fff, $1fff);		{a bit lighter than neutral gray}
		myForeColor := MakeRGBColor(0, 0, 0);								{black}
		myBackColor := MakeRGBColor(-1, -1, -1);						{white}

		myPal := GetNewPalette(kPalette);
	END; {InitGlobals}


{+--------------------------------------------------------------------------+
 |		set up menus and draw menubar																					|
 +--------------------------------------------------------------------------+}
  PROCEDURE SetUpMenus;

	VAR
	  i: Integer;

	BEGIN
	  myMenus[appleM] := GetMenu(appleID);
	  AddResMenu(myMenus[appleM], 'DRVR');
	  myMenus[fileM] := GetMenu(fileID);
	  myMenus[editM] := GetMenu(editID);
		myMenus[controlM] := GetMenu(controlID);

	  FOR i := 1 TO menuCount DO
			InsertMenu(myMenus[i], 0);
	  DrawMenuBar;
	END; {SetUpMenus}


{+--------------------------------------------------------------------------+
 |		program initialization																								|
 +--------------------------------------------------------------------------+}
	PROCEDURE Initialize;

	VAR
		x:	LongInt;
		b:	Boolean;

 	BEGIN
		InitGraf(@thePort);
		InitFonts;
		FlushEvents(everyEvent, 0);
		InitWindows;
		InitMenus;
		TEInit;
		InitDialogs(Nil);
		InitCursor;
		InitCursorCtl(Nil);			{bring in acur resource now}
		SetCursor(GetCursor(watchCursor)^^);

		InitGlobals;						{initialize my globals}
		SetUpMenus;							{set up menus and menu bar}
		b := GetNextEvent(everyEvent, myEvent);	{***juggler hacking so windows show up on top}
		ChangePalette;					{bring up palette (if option on) }
		InitFrames;							{load and process frames}
		IF slotVBL THEN
			InstallVBLSync;				{install custom VBL task if possible, do this last}
		Delay(30, x);						{wait a bit for the load dialog to go away}
		ChangeWindow;						{bring up the window invisibly}
		ShowWindow(myWindow);
	END; {Initialize}


{+--------------------------------------------------------------------------+
 |		return default top,left coords taking into account window type				|
 +--------------------------------------------------------------------------+}
	PROCEDURE DefaultWindow(VAR left, top: Integer);

	BEGIN
		IF roundWindow THEN
			BEGIN
				top := 64;
				left := 64;
			END
		ELSE
			BEGIN
				top := 64 - 16;	{account for window border}
				left := 64 - 16;
			END;
	END; {DefaultWindow}


{+--------------------------------------------------------------------------+
 |		get defaults																													|
 +--------------------------------------------------------------------------+}
	PROCEDURE GetDefaults;

	VAR
		refNum:		Integer;
		tHandle:	Handle;

	BEGIN
		refNum := OpenResFile(frameFile);
		IF refNum = -1 THEN
			FatalError(ioErrorID);
		tHandle := GetResource('Data', dataID);
		BlockMove(tHandle^, @myData, SIZEOF(dataRec));
		IF SizeResource(tHandle) <> SIZEOF(dataRec) THEN
			BEGIN
				SetHandleSize(tHandle, SIZEOF(dataRec));
				ChangedResource(tHandle);
				DefaultWindow(myData.left, myData.top);
				myData.speed := 0;
				myData.palette := 0;
			END;

		IF refNum <> appRefNum THEN
			CloseResFile(refNum);

		WITH myData DO
			BEGIN
				frameWidth := width;
				frameHeight := width;
				frameCount := count;
				syncVBL := sync = 1;
				displayTime := time = 1;
				roundWindow := round = 1;
				usePalette := palette = 1;
				windTop := top;
				windLeft := left;
			END; {WITH}
		speed := myData.speed;
		delayCount := speed;
	END; {GetDefaults}


  {$S Main}		{normal stuff goes here}

{+--------------------------------------------------------------------------+
 |		is the Option key being held down?																		|
 +--------------------------------------------------------------------------+}
	FUNCTION CheckOptionKey: Boolean;

	VAR
		keys:	KeyMap;

	BEGIN
		GetKeys(keys);
		CheckOptionKey := keys[kOptionKeyCode];
	END; {CheckOptionKey}


{+--------------------------------------------------------------------------+
 |		return the max device the globe window is in													|
 +--------------------------------------------------------------------------+}
	FUNCTION GetMyDevice: GDHandle;

	VAR
		windowRect: Rect;

	BEGIN
		IF myWindow <> Nil THEN
			BEGIN
				windowRect := myWindow^.portRect;
				LocalToGlobal(windowRect.topLeft);
				LocalToGlobal(windowRect.botRight);
			END
		ELSE
			SetRect(windowRect, windLeft, windTop, windLeft + frameWidth, windTop + frameHeight);
		GetMyDevice := GetMaxDevice(windowRect);
	END; {GetMyDevice}


{+--------------------------------------------------------------------------+
 |		bring up a dialog centered on the target device												|
 +--------------------------------------------------------------------------+}
	FUNCTION GetMyDialog(id: Integer): DialogPtr;

	VAR
		deviceRect:	Rect;
		width:			Integer;
		theDialog:	DialogPtr;

	BEGIN
		SetCursor(arrow);
	  theDialog := GetNewDialog(id, Nil, WindowPtr( -1));
		deviceRect := GetMyDevice^^.gdRect;
		width := theDialog^.portRect.right - theDialog^.portRect.left;
		MoveWindow(theDialog, (((deviceRect.right - deviceRect.left) - width) DIV 2) + deviceRect.left,
								deviceRect.top + ((deviceRect.bottom - deviceRect.top) DIV 6), False);
		ShowWindow(theDialog);
		BeginUpdate(theDialog);
		DrawDialog(theDialog);
		EndUpdate(theDialog);

		GetMyDialog := theDialog;
	END; {GetMyDialog}


{+--------------------------------------------------------------------------+
 |		update the 'Data' config resource																			|
 +--------------------------------------------------------------------------+}
	PROCEDURE SaveDefaults;

	VAR
		tHandle:	Handle;
		refNum:		Integer;
		p:				Point;

	BEGIN
		IF NOT CheckOptionKey THEN		{update the Data config resource}
			BEGIN
				refNum := OpenResFile(frameFile);
				IF refNum <> -1 THEN
					BEGIN
						p := myWindow^.portRect.topLeft;
						LocalToGlobal(p);
						WITH myData DO
							BEGIN
								IF syncVBL THEN
									sync := 1
								ELSE
									sync := 0;
								IF displayTime THEN
									time := 1
								ELSE
									time := 0;
								IF roundWindow THEN
									round := 1
								ELSE
									round := 0;
								top := p.v;
								left := p.h;
								IF usePalette THEN
									palette := 1
								ELSE
									palette := 0;
							END; {WITH}
						myData.speed := speed;
						tHandle := GetResource('Data', dataID);
						BlockMove(@myData, tHandle^, SIZEOF(dataRec));
						ChangedResource(tHandle);
						IF refNum <> appRefNum THEN
							CloseResFile(refNum)
						ELSE
							UpdateResFile(refNum);
					END;
			END;
	END; {SaveDefaults}


{+--------------------------------------------------------------------------+
 |		wait for the vertical blanking for smooth blits												|
 +--------------------------------------------------------------------------+}
	PROCEDURE WaitVBL;

	VAR
		t:	LongInt;

	BEGIN
		IF slotVBL THEN
			BEGIN
				t := ParisTicks;
				REPEAT UNTIL t <> ParisTicks;
			END
		ELSE
			BEGIN
				t := LongIntPtr(Ticks)^;
				REPEAT UNTIL t <> LongIntPtr(Ticks)^;
			END;
	END; {WaitVBL}


{+--------------------------------------------------------------------------+
 |		ensures the ENTIRE window is visible																	|
 +--------------------------------------------------------------------------+}
	PROCEDURE CheckWindowBounds(w: WindowPtr);

	VAR
		r:				Rect;
		rgn:			RgnHandlePtr;

	BEGIN
		rgn := RgnHandlePtr(GrayRgn);
		r := w^.portRect;
		IF NOT roundWindow THEN
			r.top := r.top - 20;			{account for title bar}
		SetPort(w);
		LocalToGlobal(r.topLeft);
		LocalToGlobal(r.botRight);
		IF NOT (PtInRgn(r.topLeft, rgn^) AND PtInRgn(r.botRight, rgn^)) THEN
			BEGIN
				SysBeep(1);
				DefaultWindow(windLeft, windTop);
				MoveWindow(myWindow, windLeft, windTop, False);
			END;
	END; {CheckWindowBounds}


{+--------------------------------------------------------------------------+
 |		change ChangePalette																									|
 +--------------------------------------------------------------------------+}
	PROCEDURE ChangePalette;

	VAR
		myDevice: GDHandle;

	BEGIN
		IF usePalette THEN
			BEGIN
				SetPalette(WindowPtr(-1), myPal, True);
				myDevice := GetMyDevice;
				IF myDevice^^.gdPMap^^.pixelSize <= 8 THEN
					MakeITable(myDevice^^.gdPMap^^.pmTable, myDevice^^.gdITable, 5);
			END
		ELSE
			SetPalette(WindowPtr(-1), Nil, True)
	END; {ChangePalette}


{+--------------------------------------------------------------------------+
 |		change WDEF																														|
 +--------------------------------------------------------------------------+}
	PROCEDURE ChangeWindow;

	VAR
		b:	Boolean;
		w:	Integer;
		first:	Boolean;
		p:			Point;

	BEGIN
		IF myWindow = Nil THEN
			first := True
		ELSE
			first := False;
		IF NOT first THEN
			BEGIN
				p := myWindow^.portRect.topLeft;
				LocalToGlobal(p);
				IF roundWindow THEN							{adjust for gray space border}
					BEGIN
						p.h := p.h + 16;						{danger! danger, hardcoding ahead}
						p.v := p.v + 16;
					END
				ELSE
					BEGIN
						p.h := p.h - 16;
						p.v := p.v - 16;
					END;
				DisposeWindow(myWindow);				{waste my window prior to change, if any}
			END;
		IF roundWindow THEN
			w := roundID
		ELSE
			w := rectID;
		IF usePixMaps THEN			{bring up invisible window}
			WindowPtr(myWindow) := GetNewCWindow(w, Nil, POINTER( -1))
		ELSE
			myWindow := GetNewWindow(w, Nil, POINTER( -1));
		SetPort(myWindow);
		TextMode(srcXor);
		TextSize(9);						{draw with small chars}
		BackPat(ltGray);				{set erase pattern}
		IF roundWindow THEN
			border := 0
		ELSE
			border := 32;
		SizeWindow(myWindow, frameBounds.right + border, frameBounds.bottom + border, False);
		destBounds := frameBounds;
		OffSetRect(destBounds, border DIV 2, border DIV 2);	{center a bit}
		IF NOT roundWindow THEN
			BEGIN
				timeRect := myWindow^.portRect;
				timeRect.bottom := timeRect.bottom - 5;
				timeRect.top := timeRect.bottom - 11;
				timeRect.left := timeRect.left + 5;
				timeRect.right := timeRect.left + 25;
			END;
		IF first THEN
			MoveWindow(myWindow, windLeft, windTop, False)
		ELSE
			BEGIN
				MoveWindow(myWindow, p.h, p.v, False);
				ShowWindow(myWindow);							{window is now visible}
			END;

		CheckWindowBounds(myWindow);
		b := 	BooleanPtr(PaintWhite)^;		{don't draw window in white}
		BooleanPtr(PaintWhite)^ := False;	{ when making it visible}
		UpdateWindow;											{draw everything now}
		BooleanPtr(PaintWhite)^ := b;			{restore the global}
	END; {ChangeWindow}


{+--------------------------------------------------------------------------+
 |		write the blit speed in frames per sec																|
 +--------------------------------------------------------------------------+}
	PROCEDURE WriteSpeed;

	VAR
		outStr: 			Str255;
		r:						Rect;
		savePort:			GrafPtr;

	BEGIN
		IF (targTime < TickCount) THEN
			BEGIN
				{$IFC Debug}
				IntegerPtr(MonkeyLives)^ := framesDone;		{*** for Bruce}
				{$ENDC}
				IF NOT roundWindow THEN
					BEGIN
						GetPort(savePort);
						SetPort(myWindow);
						NumToString (framesDone, outStr);
						FrameRect(timeRect);
						r := timeRect;
						InsetRect(r, 1, 1);
						PenPat(white);
						PaintRect(r);
						PenNormal;
						IF framesDone < 1000 THEN			{don't draw if too long}
							BEGIN
								MoveTo(timeRect.left + 4, timeRect.bottom - 2);
								DrawString (outStr);
							END;
						SetPort(savePort);
					END;
				framesDone := 0;
				targTime := TickCount + 60;	{1 second into the future}
			END
		ELSE
			framesDone := framesDone + 1;
	END; {WriteSpeed}


{+------------------------------------------------------------------------+
 |		change time display																									|
 +------------------------------------------------------------------------+}
	PROCEDURE FlipTime;

	VAR
		savePort:	GrafPtr;

	BEGIN
		IF displayTime THEN
			BEGIN
				displayTime := False;
				GetPort(savePort);
				SetPort(myWindow);
				IF usePixMaps THEN
					BEGIN
						RGBForeColor(spaceColor);
						PaintRect(timeRect);
						RGBForeColor(myForeColor);
					END
				ELSE
					EraseRect(timeRect);
				SetPort(savePort);
			END
		ELSE
			BEGIN
				displayTime := True;
				targTime := 0;	{draw it now}
				WriteSpeed;
			END;
	END; {FlipTime}


{+--------------------------------------------------------------------------+
 |		blit a frame - look for updates here to minimize flicker							|
 +--------------------------------------------------------------------------+}
	PROCEDURE DrawFrame(i: Integer);

	BEGIN
		IF EmptyRgn(WindowPeek(myWindow)^.updateRgn) THEN
			BEGIN
				IF syncVBL THEN
					WaitVBL;
				IF usePixMaps THEN
					{$IFC Debug}
					IF (CheckOptionKey AND UseShading) THEN
						CopyBits(BitMapPtr(pixMaps[0]^)^,
										 BitMapPtr(CGrafPtr(myWindow)^.portPixMap^)^,
										 frameBounds, destBounds, srcCopy, Nil)
					ELSE
					{$ENDC}
						CopyBits(BitMapPtr(pixMaps[i]^)^,
										 BitMapPtr(CGrafPtr(myWindow)^.portPixMap^)^,
										 frameBounds, destBounds, srcCopy, Nil)
				ELSE
					CopyBits(bitMaps[i], myWindow^.portBits, frameBounds, destBounds, srcCopy, Nil);
			END {no update}
		ELSE
			UpdateWindow;
	END; {DrawFrame}


{+--------------------------------------------------------------------------+
 |		handle update events for my window																		|
 +--------------------------------------------------------------------------+}
	PROCEDURE UpdateWindow;

	VAR
		r:	Rect;

	BEGIN
		targTime := 0;							{force draw of time box now}
		BeginUpdate(myWindow);
		IF NOT roundWindow THEN			{only need to erase/gray if border exists}
			IF usePixMaps THEN
				BEGIN
					RGBForeColor(spaceColor);
					PaintRect(myWindow^.portRect);
					RGBForeColor(myForeColor);
				END
			ELSE
				EraseRect(myWindow^.portRect);
		r := myWindow^.portRect;		{draw some nice lines}
		PenSize(1, 1);
		FrameRect(r);
		InsetRect(r, 2, 2);
		FrameRect(r);
		PenPat(white);
		InsetRect(r, -1, -1);
		FrameRect(r);
		PenNormal;
		IF displayTime THEN
			WriteSpeed;
		DrawFrame(curFrame);
		EndUpdate(myWindow);
	END; {UpdateWindow}


{+--------------------------------------------------------------------------+
 |		advance a frame - called from MEL, handles delays											|
 +--------------------------------------------------------------------------+}
	PROCEDURE FrameAdvance;

	BEGIN
		IF (delayCount = 0) AND (speed <> -1) THEN		{handle stepping}
			BEGIN
				delayCount := speed;
				IF curFrame = frameCount THEN
					curFrame := 2
				ELSE
					curFrame := curFrame + 1;

				IF displayTime THEN
					WriteSpeed;
				DrawFrame(curFrame);
			END
		ELSE
			BEGIN
				delayCount := delayCount - 1;
			END;
	END; {FrameAdvance}


{+--------------------------------------------------------------------------+
 |		about box handler																											|
 +--------------------------------------------------------------------------+}
	PROCEDURE DoAbout;

	VAR
		theDialog:	DialogPtr;
		hasEvent:		Boolean;

	BEGIN
	  theDialog := GetMyDialog(aboutID);

		REPEAT
			IF juggler THEN
				hasEvent := WaitNextEvent(keyDownMask + mDownMask, myEvent, 0, Nil)
			ELSE
				BEGIN
					SystemTask;
					hasEvent := GetNextEvent(keyDownMask + mDownMask, myEvent);
				END;
				FrameAdvance;
		UNTIL hasEvent;

		DisposDialog(theDialog);
	END; {DoAbout}


{+--------------------------------------------------------------------------+
 |		update menus																													|
 +--------------------------------------------------------------------------+}
	PROCEDURE AdjustMenus;

	VAR
		savePort:		GrafPtr;
		globalRect:	Rect;
		myDevice:		GDHandle;

	BEGIN
		GetPort(savePort);
		SetPort(myWindow);
		SetPort(savePort);
		globalRect := myWindow^.portRect;
		LocalToGlobal(globalRect.topLeft);
		LocalToGlobal(globalRect.botRight);
		myDevice := GetMyDevice;
		globalRect.left := (globalRect.left + destBounds.left) * myDevice^^.gdPMap^^.pixelSize;
		IF globalRect.left MOD 32 = 0 THEN
			DisableItem(myMenus[controlM], alignCommand)
		ELSE
			EnableItem(myMenus[controlM], alignCommand);

		IF speed = 0 THEN
			DisableItem(myMenus[controlM], fasterCommand)
		ELSE
			EnableItem(myMenus[controlM], fasterCommand);
		IF roundWindow THEN
			DisableItem(myMenus[controlM], timeCommand)
		ELSE
			EnableItem(myMenus[controlM], timeCommand);
		CheckItem(myMenus[controlM], syncCommand, syncVBL);
		CheckItem(myMenus[controlM], timeCommand, displayTime AND NOT roundWindow);
		CheckItem(myMenus[controlM], roundCommand, roundWindow);
		CheckItem(myMenus[controlM], paletteCommand, usePalette);

		IF (FrontWindow = myWindow) THEN
			BEGIN
				DisableItem(myMenus[fileM], closeCommand);
				DisableItem(myMenus[editM], undoCommand);
				DisableItem(myMenus[editM], cutCommand);
				DisableItem(myMenus[editM], copyCommand);
				DisableItem(myMenus[editM], pasteCommand);
				DisableItem(myMenus[editM], clearCommand);
			END
		ELSE
			BEGIN
				EnableItem(myMenus[fileM], closeCommand);
				EnableItem(myMenus[editM], undoCommand);
				EnableItem(myMenus[editM], cutCommand);
				EnableItem(myMenus[editM], copyCommand);
				EnableItem(myMenus[editM], pasteCommand);
				EnableItem(myMenus[editM], clearCommand);
			END;
	END; {AdjustMenus}


{+--------------------------------------------------------------------------+
 |	 longword align the window to help out quickdraw												|
 +--------------------------------------------------------------------------+}
	PROCEDURE AlignWindow;

	VAR
		i:				Integer;
		myDevice:	GDHandle;
		grid:			Integer;
		globalRect:	Rect;

	BEGIN
		globalRect := myWindow^.portRect;
		LocalToGlobal(globalRect.topLeft);
		LocalToGlobal(globalRect.botRight);

		myDevice := GetMyDevice;
		grid := 32 DIV myDevice^^.gdPMap^^.pixelSize;

		i := (((globalRect.left + destBounds.left) + grid DIV 2) DIV grid) * grid - destBounds.left;
		MoveWindow(myWindow, i, globalRect.top, False);
	END; {AlignWindow}


{+--------------------------------------------------------------------------+
 |		handle control menu commands																					|
 +--------------------------------------------------------------------------+}
	PROCEDURE DoControlMenu(i: Integer);

	VAR
		savePort:	GrafPtr;

	BEGIN
		CASE i OF
			slowerCommand:
				BEGIN
					IF speed = -1 THEN
						speed := 50			{magic numbers...}
					ELSE
						speed := speed + 1;
					delayCount := speed;
				END;

			stepCommand:
				BEGIN
					delayCount := 0;	{advance frame now}
					speed := 0;				{step now}
					FrameAdvance;
					speed := -1;			{don't advance in loop}
				END;

			fasterCommand:
				BEGIN
					speed := speed - 1;
					IF speed < 0 THEN
						speed := 0;
					delayCount := speed;
				END;

			syncCommand:
				syncVBL := NOT syncVBL;

			alignCommand:
				AlignWindow;

			timeCommand:
				FlipTime;

			roundCommand:
				BEGIN
					roundWindow := NOT roundWindow;
					ChangeWindow;
				END;

			paletteCommand:
				BEGIN
					usePalette := NOT usePalette;
					ChangePalette;
				END;

		END; {CASE}
		AdjustMenus;
	END; {DoControlMenu}


{+--------------------------------------------------------------------------+
 |		handle menu and menu key commands																			|
 +--------------------------------------------------------------------------+}
  PROCEDURE DoCommand(mResult: LONGINT);

	VAR
	  theItem:	Integer;				{menu item number from mResult low-order word}
	  theMenu:	Integer;				{menu number from mResult high-order word}
	  name:			Str255;					{desk accessory name}
	  w:				WindowPtr;
		i:				Integer;

	BEGIN
	  theItem := LoWord(mResult);	   {call Toolbox Utility routines to}
	  theMenu := HiWord(mResult);	   {set menu item number and menu}
	  {number}

	  CASE theMenu OF
			appleID:
					BEGIN
						IF (theItem = aboutCommand) THEN
							DoAbout
						ELSE
							BEGIN
								GetItem(myMenus[appleM], theItem, name);
								i := OpenDeskAcc(name);
								SetPort(myWindow);
							END;
					END; {CASE appleID}

			fileID:
				CASE theItem OF
					closeCommand:
						BEGIN
							w := FrontWindow;
							IF WindowPeek(w)^.windowKind < 0 THEN
								CloseDeskAcc(WindowPeek(w)^.windowKind);
						END;
					quitCommand:
						doneFlag := True;
				END; {CASE fileID}

			editID:
				IF NOT SystemEdit(theItem - 1) THEN
					BEGIN
					END;

			controlID:
				DoControlMenu(theItem);

	  END; {CASE theMenu}

	  HiliteMenu(0);
	END; {DoCommand}


{+--------------------------------------------------------------------------+
 |		main event loop																												|
 +--------------------------------------------------------------------------+}
	PROCEDURE MainEventLoop;

	VAR
		hasEvent:			Boolean;
		whichWindow:	WindowPtr;

	BEGIN
		REPEAT
			FrameAdvance;							{draw my stuff}

			IF juggler THEN
				hasEvent := WaitNextEvent(everyEvent, myEvent, 0, Nil)
			ELSE
				BEGIN
					SystemTask;						{service DAs}
					hasEvent := GetNextEvent(everyEvent, myEvent);
				END;

			IF hasEvent THEN
				CASE myEvent.what OF

					mouseDown:
						CASE FindWindow(myEvent.where, whichWindow) OF

							inSysWindow:
								SystemClick(myEvent, whichWindow);

							inMenuBar:
								BEGIN
									AdjustMenus;	{pretty up the menus}
									DoCommand(MenuSelect(myEvent.where));
								END;

							inDrag:
								BEGIN
									DragWindow(whichWindow, myEvent.where, dragRect);
									AdjustMenus;	{align command may change}
								END;

							inGoAway:
								IF whichWindow = myWindow THEN
									IF TrackGoAway(myWindow, myEvent.where) THEN
										doneFlag := True;

							inContent:
								BEGIN
									IF whichWindow <> FrontWindow THEN
										SelectWindow(whichWindow);
									IF roundWindow THEN
										BEGIN
											DragWindow(whichWindow, myEvent.where, dragRect);
											AdjustMenus;	{align command may change}
										END;
								END;
						END; {CASE mouseDown}

					keyDown, autoKey: 		   {key pressed once or held down to repeat}
						IF (myWindow = frontWindow) AND
							 (BAnd(myEvent.modifiers, cmdKey) <> 0) THEN
							DoCommand(MenuKey(CHR(BAnd(myEvent.message, charCodeMask))));

					activateEvt:
						IF (WindowPtr(myEvent.message) = myWindow) AND
							 (BAnd(myEvent.modifiers, activeFlag) <> 0) THEN
							SetCursor(arrow);

					updateEvt:
						IF WindowPtr(myEvent.message) = myWindow THEN
							UpdateWindow;

				END; {CASE myEvent.what}
		UNTIL doneFlag;
  END; {MainEventLoop}


{+--------------------------------------------------------------------------+
 |		recursively chow da windows																						|
 +--------------------------------------------------------------------------+}
	PROCEDURE CloseAllWindows(w: WindowPtr);

	BEGIN
		IF WindowPeek(w)^.nextWindow <> Nil THEN
			CloseAllWindows(WindowPtr(WindowPeek(w)^.nextWindow));
		IF WindowPeek(w)^.windowKind < 0 THEN
			CloseDeskAcc(WindowPeek(w)^.windowKind);
	END; {CloseAllWindows}


{+--------------------------------------------------------------------------+
 |		Main Program																													|
 +--------------------------------------------------------------------------+}
	BEGIN
		UnLoadSeg(@_DataInit);				{remove Pascal data initialization code}
		MaxApplZone;									{fully expand the heap}
		Initialize;										{run once-only code}
		UnLoadSeg(@Initialize);				{remove once-only code}

		AdjustMenus;									{adjust once now}
		MainEventLoop;								{run the main loop}

		IF NOT juggler THEN
			SetCursor(GetCursor(watchCursor)^^);
		IF slotVBL THEN
			RemoveVBLSync;							{remove custom VBL task if on Mac II}
		SaveDefaults;									{save the program settings}
		DisposeWindow(myWindow);
		IF FrontWindow <> Nil THEN
			CloseAllWindows(FrontWindow);
	END. {PROGRAM Globe}
